package com.baykalcakici.cafepicker.picker;

import java.io.IOException;
import java.io.StringReader;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Random;

import org.joda.time.DateTime;
import org.joda.time.LocalTime;

import android.util.Log;
import android.util.Pair;
import au.com.bytecode.opencsv.CSVReader;

import com.baykalcakici.cafepicker.util.FtClient;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

public class CafePicker {
  private static final String CAFE_LIST = "Cafe List";
  private static final String CAFE_HISTORY = "Cafe History";
  private Map<String, CafeListItem> cafeMap;
  private List<CafeHistoryItem> cafeHistory;
  private FtClient ftClient;
  String cafeListTableId = null;
  String cafeHistoryTableId = null;

  public CafePicker(FtClient ftClient) {
    this.ftClient = Preconditions.checkNotNull(ftClient);
    initializeIfNecessary();
  }

  public void initializeIfNecessary() {
    try {
      if (cafeListTableId == null || cafeHistoryTableId == null) {
        initializeTableIds();
      }
      if (cafeMap == null || cafeMap.isEmpty()) {
        initializeCafeMap(cafeListTableId);
      }
      if (cafeHistory == null || cafeHistory.isEmpty()) {
        initializeCafeHistory(cafeHistoryTableId);
      }
    } catch (IOException e) {
      // Nothing yet
    }
  }

  private void initializeTableIds() throws IOException {
    String req = ftClient.query("SHOW TABLES");
    Log.d("cafepicker", "initTableIds: " + req);
    if (req != null) {
      CSVReader reader = new CSVReader(new StringReader(req));
      while ((cafeListTableId == null) || (cafeHistoryTableId == null)) {
        String[] showTablesResult;
        showTablesResult = reader.readNext();
        if (showTablesResult == null) {
          break;
        }

        if (showTablesResult[1].equals(CAFE_LIST)) {
          cafeListTableId = showTablesResult[0];
        } else if (showTablesResult[1].equals(CAFE_HISTORY)) {
          cafeHistoryTableId = showTablesResult[0];
        }
      }
    }
  }

  /** Initializes available cafes map */
  private void initializeCafeMap(String cafeListTableId) throws IOException {
    cafeMap = Maps.newHashMap();
    String req = ftClient.query("SELECT * FROM " + cafeListTableId);
    Log.d("cafepicker", "createCafeMap: " + req);
    if (req != null) {
      CSVReader reader = new CSVReader(new StringReader(req));
      while (true) {
        String row[] = reader.readNext();
        if (row == null) {
          break;
        } else if (row[0].equals("Name")) {
          continue;
        } else {
          CafeListItem cafe = new CafeListItem(row);
          cafeMap.put(cafe.name, cafe);
        }
      }
    }
  }

  private void initializeCafeHistory(String cafeHistoryTableId) throws IOException {
    cafeHistory = Lists.newArrayList();
    String req = ftClient.query("SELECT * FROM " + cafeHistoryTableId);
    Log.d("cafepicker", "createCafeHistory: " + req);
    if (req != null) {
      CSVReader reader = new CSVReader(new StringReader(req));
      while (true) {
        String row[] = reader.readNext();
        if (row == null) {
          break;
        } else if (row[0].equals("Name")) {
          continue;
        } else {
          CafeHistoryItem cafe = new CafeHistoryItem(row);
          cafeHistory.add(cafe);
        }
      }
      Collections.sort(cafeHistory);
    }
  }

  public Pair<String, Double> pick() {
    Pair<String, Double> result = null;
    try {
      LocalTime currentTime = new LocalTime();
      final byte nameMask = CafeListItem.getCafeNameServingMask(currentTime);
      final byte historyMask = CafeListItem.getCafeHistoryServingMask(currentTime);

      List<String> cafeNamesList = getCafeNames(nameMask);
      List<String> cafeHistoryList = getCafeHistory(historyMask);

      Map<String, Double> cafeWeights = Maps.newHashMap();
      setInitialCafeWeights(cafeNamesList, cafeWeights);
      setWeightsBasedOnHistory(cafeHistoryList, cafeWeights);

      // find the total weight
      Double totalWeight = 0.0;
      for (Double weight : cafeWeights.values()) {
        totalWeight += weight;
      }

      Double randomNumber = new Random().nextDouble() * totalWeight;
      for (Map.Entry<String, Double> entry : cafeWeights.entrySet()) {
        if (randomNumber < entry.getValue()) {
          result = Pair.create(entry.getKey(), entry.getValue() / totalWeight);
          break;
        }
        randomNumber -= entry.getValue();
      }
    } catch (IOException e) {
      result = Pair.create("Error Occured", -1.0);
    }
    return result;
  }

  public void visit(String cafe) {
    cafe = cafe.replaceFirst("\\'", "\\\\'");
    StringBuilder builder = new StringBuilder();
    builder.append("INSERT INTO ");
    builder.append(cafeHistoryTableId);
    builder.append(" (Name, Time) VALUES ('");
    builder.append(cafe);
    builder.append("', '");
    builder.append(new DateTime());
    builder.append("')");
    String query = builder.toString();
    Log.d("cafepicker", query);
    ftClient.query(query);
    cafeHistory = null;
    initializeIfNecessary();
  }

  public String getCafeUrl(String cafe) {
    return cafeMap.get(cafe).url;
  }

  public String getCafeLocation(String cafe) {
    return cafeMap.get(cafe).location;
  }

  public List<String> getCafeNames(final byte mask) {
    List<String> cafeNameList = Lists.newArrayList();
    for (CafeListItem cafe : cafeMap.values()) {
      if (cafe.isServing(mask)) {
        cafeNameList.add(cafe.name);
      }
    }
    Collections.sort(cafeNameList);
    return cafeNameList;
  }

  public List<String> getCafeHistory(final byte mask) throws IOException {
    List<String> cafeHistoryList = Lists.newArrayList();
    for (CafeHistoryItem cafeHistoryItem : cafeHistory) {
      CafeListItem cafeListItem = cafeMap.get(cafeHistoryItem.name);
      if (cafeListItem != null
          && cafeListItem.isServing(mask)
          && (mask == CafeListItem
              .getCafeHistoryServingMask(cafeHistoryItem.dateTime.toLocalTime()))) {
        cafeHistoryList.add(cafeHistoryItem.name);
      }
    }
    return cafeHistoryList;
  }

  public List<CafeHistoryItem> getPreviouslyVisitedCafes() {
    List<CafeHistoryItem> cafeHistoryItemList = Lists.newArrayList();
    for (CafeHistoryItem cafeHistoryItem : cafeHistory) {
      cafeHistoryItemList.add(cafeHistoryItem);
    }
    return cafeHistoryItemList;
  }

  private void setWeightsBasedOnHistory(List<String> cafeHistoryList,
      Map<String, Double> cafeWeights) {
    int count = 1;
    for (String cafe : cafeHistoryList) {
      if (cafeWeights.containsKey(cafe)) {
        cafeWeights.put(cafe, cafeWeights.get(cafe) * getWeightForCount(count++));
      }
    }
    for (Map.Entry<String, Double> entry : cafeWeights.entrySet()) {
      Log.d("cafepicker", entry.getKey() + ": " + entry.getValue());
    }
  }

  /**
   * Function that returns a weight given a count. Courtesy of our own famed
   * DSEA Misha.
   */
  private Double getWeightForCount(int count) {
    return 1 / (1+ Math.exp(4.0 - count / 2.0));
  }

  private void setInitialCafeWeights(List<String> cafeNamesList, Map<String, Double> cafeWeights) {
    for (String cafe : cafeNamesList) {
      cafeWeights.put(cafe, 1.0);
    }
  }
}
